package com.gitee.wsl.compose.ui.swiperefresh.sample

import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.zIndex
import com.gitee.wsl.compose.ui.swiperefresh.layout.RefreshSubComposeLayout
import com.gitee.wsl.compose.ui.swiperefresh.layout.obtainContentOffset
import com.gitee.wsl.compose.ui.swiperefresh.layout.obtainFooterOffset
import com.gitee.wsl.compose.ui.swiperefresh.layout.obtainHeaderOffset
import com.gitee.wsl.compose.ui.swiperefresh.layout.obtainZIndex
import com.gitee.wsl.compose.ui.swiperefresh.state.ActionStatus
import com.gitee.wsl.compose.ui.swiperefresh.sample.state.Action
import com.gitee.wsl.compose.ui.swiperefresh.state.NestedScrollMode
import com.gitee.wsl.compose.ui.swiperefresh.sample.state.RefreshLayoutState
import com.gitee.wsl.compose.ui.swiperefresh.sample.state.RefreshNestedScrollConnection
import com.gitee.wsl.compose.ui.swiperefresh.sample.state.UltraSwipeRefreshNestedScrollConnection
import com.gitee.wsl.compose.ui.swiperefresh.sample.state.rememberRefreshLayoutState
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.collectLatest
import timber.log.Timber


/**
 * A refresh layout that can be used to refresh and load more data.
 *
 * The refresh layout is composed of a header and a footer, and the content is placed in the middle.
 * When the content is pulled down, the header will be displayed, and when the content is pulled up,
 * the footer will be displayed.
 *
 * Thanks Github Copilot for helping me write this comment :)
 *
 *
 * @param state The state of the refresh layout. You can use [rememberRefreshLayoutState] to create one.
 * @param modifier The modifier to be applied to the layout.
 * @param header The header of the refresh layout. Default is [DefaultRefreshHeader].
 * @param footer The footer of the refresh layout. Default is [DefaultRefreshFooter].
 * @param enableRefresh Whether to enable refresh.
 * @param enableLoadMore Whether to enable load more.
 * @param onRefresh The callback to be invoked when the refresh action is triggered.
 * @param onLoadMore The callback to be invoked when the load more action is triggered.
 * @param contentScrollState The scroll state of the content, used to scroll the content when loading more.
 * @param content The content of the refresh layout.
 *
 * @see RefreshLayoutState for more information about the state of the refresh layout.
 * @see RefreshNestedScrollConnection for more information about the nested scroll connection.
 */
@Composable
fun RefreshLayout(
    modifier: Modifier = Modifier,
    state: RefreshLayoutState = rememberRefreshLayoutState(),
    header: @Composable BoxScope.(Action.Refreshing) -> Unit = {
        DefaultRefreshHeader(state = it)
    },
    footer: @Composable BoxScope.(Action.LoadingMore) -> Unit = {
        DefaultRefreshFooter(state = it)
    },
    enableRefresh: Boolean = true,
    enableLoadMore: Boolean = true,
    onRefresh: () -> Unit = {},
    onLoadMore: () -> Unit = {},
    contentScrollState: ScrollableState? = null,
    scrollMode: NestedScrollMode = NestedScrollMode.Translate,
    //For some special cases, you may need to set the padding of the footer to make it float on the content(there may be something cover the footer).
    footerPaddingBottom: Dp = Dp.Unspecified,
    content: @Composable BoxScope.(RefreshLayoutState) -> Unit,
) {

    var headerHeight by remember { mutableIntStateOf(0) }
    var footerHeight by remember { mutableIntStateOf(0) }
    val coroutineScope = rememberCoroutineScope()
    val conn = remember {
        UltraSwipeRefreshNestedScrollConnection(
            state = state,
            coroutineScope = coroutineScope,
            refreshEnabled = enableRefresh,
            loadMoreEnabled = enableLoadMore,
        )
    }.apply {
        this.refreshEnabled = enableRefresh
        this.loadMoreEnabled = enableLoadMore
    }
/*    Box(
        modifier = modifier
            .fillMaxWidth()
            .clipToBounds()
            .nestedScroll(conn)
    ) {
        //header
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .layout { measurable, constraints ->
                    val placeable = measurable.measure(constraints)
                    layout(placeable.width, placeable.height) {
                        placeable.placeRelative(0, -placeable.height)
                    }
                }
                .graphicsLayer {
                    //translate the header by the offset if we are refreshing (scrolling down)
                    translationY = if (state.indicatorOffset > 0) state.indicatorOffset else 0f
                }
                .onSizeChanged {
                    state.refreshingState.triggerDistancePx = it.height.toFloat()
                    headerHeight = it.height
                }
        ) {
            header(state.refreshingState)
        }
        //content
        Box(modifier = Modifier
            .fillMaxSize()
            .offset {
                //Move the content by the offset
                IntOffset(0, state.indicatorOffset.roundToInt())
            }) {
            content(state)
        }
        //footer
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .layout { measurable, constraints ->
                    val placeable = measurable.measure(constraints)
                    layout(placeable.width, placeable.height) {
                        placeable.placeRelative(0, placeable.height)
                    }
                }
                .then(if (footerPaddingBottom != Dp.Unspecified) Modifier.offset(y = -footerPaddingBottom) else Modifier)
                .graphicsLayer {
                    //translate the footer by the offset if we are loading more (scrolling up)
                    translationY = if (state.indicatorOffset < 0) state.indicatorOffset else 0f
                }
                .onSizeChanged {
                    state.loadingMoreState.triggerDistancePx = it.height.toFloat()
                    footerHeight = it.height
                }
                .align(Alignment.BottomCenter)
        ) {
            footer(state.loadingMoreState)
        }
    }*/

    Box(modifier) {
        RefreshSubComposeLayout(
            refreshEnabled = enableRefresh,
            loadMoreEnabled = enableLoadMore,
            headerIndicator = {
                header(state.refreshingState)
            },
            footerIndicator = {
                footer(state.loadingMoreState)
            },
        ) { headerHeight, footerHeight ->

            LaunchedEffect(state.refreshingState, enableRefresh) {
                snapshotFlow {
                    state.refreshingState.componentStatus
                }.collectLatest {
                    Timber.d( "RefreshLayout: refreshingState = $it")
                    if (it == ActionStatus.InProgress) {
                        state.animateOffsetTo(headerHeight.toFloat())
                        onRefresh()
                    } else if (it.isResetting) {
                        val stateOffsetY = state.indicatorOffset
                        val d2 = async {
                            state.animateOffsetTo(0f)
                            state.idle()
                        }
                        val d1 = async {
                            if (stateOffsetY != 0f && contentScrollState?.isScrollInProgress != true) {
                                Timber.d( "RefreshLayout: contentScrollState scroll start")
                                contentScrollState?.animateScrollBy(-stateOffsetY)
                                Timber.d( "RefreshLayout:contentScrollState scroll done ")
                            }
                        }
                        d1.await()
                        d2.await()
                    } else if (it == ActionStatus.IDLE) {
                        state.animateOffsetTo(0f)
                    }
                }
            }

            LaunchedEffect(state.loadingMoreState, enableLoadMore) {
                snapshotFlow {
                    state.loadingMoreState.componentStatus
                }.collectLatest {
                    if (it == ActionStatus.InProgress) {
                        state.animateOffsetTo(-footerHeight.toFloat())
                        onLoadMore()
                    } else if (it.isResetting) {
                        val stateOffsetY = state.indicatorOffset
                        val d2 = async {
                            state.animateOffsetTo(0f)
                            state.idle()
                        }
                        val d1 = async {
                            if (stateOffsetY != 0f && contentScrollState?.isScrollInProgress != true) {
                                contentScrollState?.animateScrollBy(-stateOffsetY)
                            }
                        }
                        d2.await()
                        d1.await()
                    } else if (it == ActionStatus.IDLE) {
                        state.animateOffsetTo(0f)
                    }
                }
            }

            Box(modifier = Modifier.nestedScroll(conn).clipToBounds()) {
                Box(modifier = Modifier
                    .align(Alignment.TopCenter)
                    .graphicsLayer {
                        translationY = obtainHeaderOffset(state.indicatorOffset, scrollMode, headerHeight)
                    }
                    .zIndex(obtainZIndex(scrollMode))
                    /*.onSizeChanged {
                        state.refreshingState.triggerDistancePx = it.height.toFloat()
                        headerHeight = it.height
                    }*/
                ) {
                    if (enableRefresh) {
                        //headerIndicator(state)
                        header(state.refreshingState)
                    }
                }
                Box(modifier = Modifier
                    .align(Alignment.BottomCenter)
                    .graphicsLayer {
                        translationY =
                            obtainFooterOffset(state.indicatorOffset, scrollMode, footerHeight)
                    }
                    .zIndex(obtainZIndex(scrollMode))
                    /*.onSizeChanged {
                        state.loadingMoreState.triggerDistancePx = it.height.toFloat()
                        footerHeight = it.height
                    }*/
                    .align(Alignment.BottomCenter)
                ) {
                    if (enableLoadMore) {
                        //footerIndicator(state)
                        footer(state.loadingMoreState)
                    }
                }
                Box(modifier = Modifier.graphicsLayer {
                    translationY = obtainContentOffset(state.indicatorOffset, scrollMode, scrollMode)
                }) {
                    content(state)
                }
            }
        }
    }
}

/**
 * Only enable refresh.
 */
@Composable
fun PullRefreshLayout(
    modifier: Modifier = Modifier,
    state: RefreshLayoutState = rememberRefreshLayoutState(),
    enableRefresh: Boolean = true,
    header: @Composable BoxScope.(Action.Refreshing) -> Unit = {
        DefaultRefreshHeader(state = it)
    },
    onRefresh: () -> Unit = {},
    content: @Composable BoxScope.(RefreshLayoutState) -> Unit
) {
    RefreshLayout(
        modifier = modifier,
        state = state,
        content = content,
        header = header,
        enableLoadMore = false,
        enableRefresh = enableRefresh,
        onRefresh = onRefresh
    )
}